# -*- coding: utf8  -*-

try:
    import configparser
except:
    import ConfigParser as configparser
import os
import six
import json
import time
import base64
import logging

from phoenix_mns.mns.account import Account
from phoenix_mns.mns.mns_exception import MNSExceptionBase
from phoenix_mns.mns.queue import Message
from phoenix_mns.mns.topic import TopicMessage
from phoenix_mns.mns_log.log import init_log_from_config, logger as default_logger


BASE = "Base"
TOPIC = "Topic"
ACCESS_KEY_SECRET = "AccessKeySecret"
END_POINT = "Endpoint"
ACCESS_ID = "AccessKeyId"
QUEUE_NAME = "QueueName"
TOPIC_NAME = "TopicName"
WAIT_SECOND = "WaitSecond"
VISIBLE_TIMEOUT = "VisibilityTimeout"
APP = "App"
ENV = "Env"
STAGE_ENDPOINT = 'http://1407376882920865.mns.cn-shanghai.aliyuncs.com'

required_ops = [(BASE, END_POINT), (BASE, QUEUE_NAME), (BASE, WAIT_SECOND)]
topic_required_ops = [(TOPIC, END_POINT), (TOPIC, TOPIC_NAME)]


class MnsClient():
    """mns client
    """

    def __init__(self, cfg_file, logger=None):
        self.cfg_file = cfg_file
        self.last_recv_msg = None
        if logger is None:
            self.logger = default_logger
        elif isinstance(logger, str):
            self.logger = init_log_from_config(logger)
        elif isinstance(logger, logging.LoggerAdapter):
            self.logger = logger
        else:
            raise Exception("unrecognized logger type: {}".format(type(logger)))
        self.init_from_cfg()

    def init_from_cfg(self):
        parser = configparser.ConfigParser()
        parser.read(self.cfg_file)
        for sec, op in required_ops:
            if not parser.has_option(sec, op):
                raise Exception("mnsclient load config file failed")
        self.wait_seconds = int(parser.get(BASE, WAIT_SECOND))
        if not parser.has_option(BASE, VISIBLE_TIMEOUT):
            self.visible_timeout = 60
        else:
            self.visible_timeout = int(parser.get(BASE, VISIBLE_TIMEOUT))

        queue_name = parser.get(BASE, QUEUE_NAME)
        if parser.has_option(BASE, ACCESS_ID) or parser.has_option(BASE, ACCESS_KEY_SECRET):
            self.logger.warning('AccessKey will be deprecated, please rmeove it from your configs {} asap.'.format(queue_name,))

        # 方式一
        # try:
        #     app = parser.get(BASE, APP) if parser.has_option(BASE, APP) else ''
        #     env = parser.get(BASE, ENV) if parser.has_option(BASE, ENV) else ''
        #     endpoint = parser.get(BASE, END_POINT)
        #     stage = os.environ.get('DXM_STAGE')
        #     if str(stage).upper() == 'TRUE':
        #         # stage环境, 替换env和endpoint
        #         env = 'stage'
        #         endpoint = STAGE_ENDPOINT
        #     arsenal = XXXClient('mns', app, env)
        #     key = arsenal.get_access_key()
        #     self.mns_account = Account(endpoint, key.access_key_id, key.access_key_secret)
        # except:
        #     self.logger.warning("failed to obtain mns ak from arsenal {} {} {}".format(queue_name, app, env))
        #     self.mns_account = Account(parser.get(BASE, END_POINT), parser.get(BASE, ACCESS_ID), parser.get(BASE, ACCESS_KEY_SECRET))
        # 方式二
        self.mns_account = Account(parser.get(BASE, END_POINT), parser.get(BASE, ACCESS_ID), parser.get(BASE, ACCESS_KEY_SECRET))

        self.mns_queue = self.mns_account.get_queue(queue_name)

    def recv_msg(self, visible_timeout=60):
        """
        @return: None means recv msg failed, otherwise, return the msg_body(means the msg that you send)
        """
        ret_flag = None
        ret_count = None
        try:
            _msg = self.mns_queue.receive_message(self.wait_seconds)
            if self.visible_timeout != visible_timeout:
                visible_timeout = self.visible_timeout
            try:
                change_msg_vis = self.mns_queue.change_message_visibility(_msg.receipt_handle, visible_timeout)
                self.logger.debug("Change Message Visibility Succeed!\nReceiptHandle:%s\nNextVisibleTime:%s\n\n" %
                                  (change_msg_vis.receipt_handle, change_msg_vis.next_visible_time))
                self.logger.debug("Receive Message Succeed. Queue: %s MessageId: %s DequeueCount: %s MessageBody: %s" % (self.mns_queue.queue_name, _msg.message_id, _msg.dequeue_count, _msg.message_body))
                ret_flag = _msg.message_body
                ret_count = _msg.dequeue_count

                self.last_recv_msg = change_msg_vis
            except MNSExceptionBase as e:
                self.logger.warning("Change Message Visibility Fail!\nQueue: %s\nException:%s\n\n" % (self.mns_queue.queue_name, e))
        except MNSExceptionBase as e:
            if e.type == 'MessageNotExist':
                self.logger.debug("no message left in queue [%s]" % self.mns_queue.queue_name)
            else:
                self.logger.warning("Receive Message Fail!\nQueue: %s\nException:%s\n\n" % (self.mns_queue.queue_name, e))
        return ret_flag, ret_count

    def del_msg(self, retry=3):
        """
        @return:
        """
        num = 0
        ret_flag = False
        if self.last_recv_msg is None:
            self.logger.debug("last recv msg is None, nothing to do")
            return True
        while num <= retry:
            num += 1
            try:
                self.mns_queue.delete_message(self.last_recv_msg.receipt_handle)
                self.logger.debug("Delete Message Succeed.\n\n")
                ret_flag = True
                self.last_recv_msg = None
                return ret_flag
            except MNSExceptionBase as e:
                self.logger.warning("Del Message Failed. Try again later!\nQueue: %s\nHandler: %s\nException:%s\n\n" % (self.mns_queue.queue_name, self.last_recv_msg.receipt_handle, e))
                time.sleep(1.0)
        self.logger.warning("Try Delete Message %s time, Still Failed! Message will be pushed back to queue [%s]" % (retry, self.mns_queue.queue_name))
        return ret_flag

    def recv_msg_with_handle(self, visible_timeout=60):
        """
        @return: None means recv msg failed, otherwise, return the msg_body(means the msg that you send)
        """
        ret_flag = None
        ret_count = None
        ret_handler = None
        try:
            _msg = self.mns_queue.receive_message(self.wait_seconds)
            if self.visible_timeout != visible_timeout:
                visible_timeout = self.visible_timeout
            try:
                change_msg_vis = self.mns_queue.change_message_visibility(_msg.receipt_handle, visible_timeout)
                self.logger.debug("Change Message Visibility Succeed!\nReceiptHandle:%s\nNextVisibleTime:%s\n\n" %
                                  (change_msg_vis.receipt_handle, change_msg_vis.next_visible_time))
                self.logger.debug("Receive Message Succeed. Queue: %s MessageId: %s DequeueCount: %s MessageBody: %s" % (self.mns_queue.queue_name, _msg.message_id, _msg.dequeue_count, _msg.message_body))
                ret_flag = _msg.message_body
                ret_count = _msg.dequeue_count
                ret_handler = change_msg_vis.receipt_handle
            except MNSExceptionBase as e:
                self.logger.warning("Change Message Visibility Fail!\nQueue: %s\nException:%s\n\n" % (self.mns_queue.queue_name, e))
        except MNSExceptionBase as e:
            if e.type == 'MessageNotExist':
                self.logger.debug("no message left in queue [%s]" % self.mns_queue.queue_name)
            else:
                self.logger.warning("Receive Message Fail!\nQueue: %s\nException:%s\n\n" % (self.mns_queue.queue_name, e))
        return ret_flag, ret_count, ret_handler

    def del_msg_by_handle(self, receipt_handle, retry=3):
        """
        @return:
        """
        num = 0
        ret_flag = False
        if receipt_handle is None:
            return True
        while num <= retry:
            num += 1
            try:
                self.mns_queue.delete_message(receipt_handle)
                self.logger.debug("Delete Message %s Succeed.\n" % receipt_handle)
                ret_flag = True
                self.last_recv_msg = None
                return ret_flag
            except MNSExceptionBase as e:
                self.logger.warning("Del Message Fail. Retry Again!\nQueue: %s\nHandler: %s\nException:%s\n\n, retry again" % (self.mns_queue.queue_name, receipt_handle, e))
                time.sleep(1.0)
        self.logger.warning("Try Delete Message %s time, Still Failed! Message will be pushed back to queue [%s]" % (retry, self.mns_queue.queue_name))
        return ret_flag

    def send_msg(self, msg_body, delay_sec=0):
        """
        @param msg_body:
        @return: None for send failed, otherwise return the send message id like 23F711056A4D9A94-2-151B021151D-2000013C4
        """
        #ret_flag = False
        send_msg_id = None

        # 添加时间戳
        try:
            timestamp = int(time.time() * 1000 * 1000)
            dic = json.loads(msg_body)
            dic.update({'a_timestamp': str(timestamp)})
            msg_body = json.dumps(dic)
        except:
            self.logger.debug("Add TimeStamp Fail.\nMessageBody:%s\n" % (msg_body,))

        message = Message(msg_body)
        message.set_delayseconds(delay_sec)
        try:
            send_msg = self.mns_queue.send_message(message)
            self.logger.debug("Send Message Succeed.\nQueue: %s\nMessageBody:%s\nMessageId:%s\nMessageBodyMd5:%s\n\n" % (self.mns_queue.queue_name, msg_body, send_msg.message_id, send_msg.message_body_md5))
            send_msg_id = send_msg.message_id
        except MNSExceptionBase as e:
            self.logger.debug("Send Message Fail.\nQueue: %s\nMessageBody:%s\n" % (self.mns_queue.queue_name, msg_body,))
            self.logger.warning("Send Message Fail!\nQueue: %s\nException:%s\n\n" % (self.mns_queue.queue_name, e))
            if six.PY3:
                if isinstance(msg_body, str):
                    msg_body = msg_body.encode()
                msg_body_b64 = base64.b64encode(msg_body).decode()
            else:
                msg_body_b64 = base64.b64encode(msg_body)
            self.logger.info("mns_action=send\tstatus=failed\tqueue={0}\treason=except\tmsg={1}".format(self.mns_queue.queue_name, msg_body_b64))
        return send_msg_id


class TopicClient(object):
    """
    mns topic client
    """

    def __init__(self, cfg_file, logger=None):
        self.cfg_file = cfg_file
        self.last_recv_msg = None
        if logger is None:
            self.logger = default_logger
        elif isinstance(logger, str):
            self.logger = init_log_from_config(logger)
        elif isinstance(logger, logging.LoggerAdapter):
            self.logger = logger
        else:
            raise Exception("unrecognized logger type: {}".format(type(logger)))
        self.init_from_cfg()

    def init_from_cfg(self):
        parser = configparser.ConfigParser()
        parser.read(self.cfg_file)
        for sec, op in topic_required_ops:
            if not parser.has_option(sec, op):
                raise Exception("mnsclient load [topic] config file failed")
        topic_name = parser.get(TOPIC, TOPIC_NAME)
        # try:
        #     app = parser.get(BASE, APP) if parser.has_option(BASE, APP) else ''
        #     env = parser.get(BASE, ENV) if parser.has_option(BASE, ENV) else ''
        #     endpoint = parser.get(TOPIC, END_POINT)
        #     stage = os.environ.get('DXM_STAGE')
        #     if str(stage).upper() == 'TRUE':
        #         # stage环境, 替换env和endpoint
        #         env = 'stage'
        #         endpoint = STAGE_ENDPOINT
        #     arsenal = ArsenalClient('topic', app, env)
        #     key = arsenal.get_access_key()
        #     self.mns_account = Account(endpoint, key.access_key_id, key.access_key_secret)
        # except:
        #     self.logger.warning("failed to obtain topic ak from arsenal {} {} {}".format(topic_name, app, env))
        #     self.mns_account = Account(parser.get(TOPIC, END_POINT), parser.get(TOPIC, ACCESS_ID), parser.get(TOPIC, ACCESS_KEY_SECRET))

        self.mns_topic = self.mns_account.get_topic(topic_name)

    def send_msg_to_queue(self, msg_body, msg_tag=""):
        """
        使用队列作为EndPoint:
            1必须指定topic数据类型为SIMPLIFIED
            2必须base64.b64encode(msg_body)
        发送广播
        :param msg_body: 消息体
        :param msg_tag: 过滤tag[可选]
        :return: send_msg_id
        """
        send_msg_id = None
        # PY3: base64.b64encode a bytes-like object is required, not 'str'
        if six.PY3:
            if isinstance(msg_body, str):
                msg_body = msg_body.encode()
            msg_body_b64 = base64.b64encode(msg_body).decode() # to str
        else:
            msg_body_b64 = base64.b64encode(msg_body)
        message = TopicMessage(msg_body_b64, msg_tag)
        try:
            re_msg = self.mns_topic.publish_message(message)
            self.logger.debug("Publish Message Succeed.\nMessageBody:%s\nMessageTag:%s\nMessageId:%s\nMessageBodyMd5:%s\n\n" % (msg_body_b64, msg_tag, re_msg.message_id, re_msg.message_body_md5))
            send_msg_id = re_msg.message_id
        except MNSExceptionBase as e:
            self.logger.warning("Publish Message Fail!\nException:%s\n\n" % e)
        return send_msg_id


if __name__ == "__main__":
    mc = MnsClient('conf/sample.cfg')
    for i in range(3850):
        work_dict_1 = {
            'tongdun_interface': 'loan',
            'user_id': 123,
            'order_id': 'sdfg-fgh-'+str(i),
            'content': 'demo'
        }
        mc.send_msg(json.dumps(work_dict_1))
